if (!dojo._hasResource["dojo.dnd.Manager"]) { //_hasResource checks added by build. Do not use _hasResource directly in your code.
    dojo._hasResource["dojo.dnd.Manager"] = true;
    dojo.provide("dojo.dnd.Manager");

    dojo.require("dojo.dnd.common");
    dojo.require("dojo.dnd.autoscroll");
    dojo.require("dojo.dnd.Avatar");

    dojo.declare("dojo.dnd.Manager", null, {
        // summary: the manager of DnD operations (usually a singleton)
        constructor: function() {
            this.avatar = null;
            this.source = null;
            this.nodes = [];
            this.copy = true;
            this.target = null;
            this.canDropFlag = false;
            this.events = [];
        },

        // avatar's offset from the mouse
        OFFSET_X: 16,
        OFFSET_Y: 16,

        // methods
        overSource: function(source) {
            // summary: called when a source detected a mouse-over conditiion
            // source: Object: the reporter
            if (this.avatar) {
                this.target = (source && source.targetState != "Disabled") ? source : null;
                this.avatar.update();
            }
            dojo.publish("/dnd/source/over", [source]);
        },
        outSource: function(source) {
            // summary: called when a source detected a mouse-out conditiion
            // source: Object: the reporter
            if (this.avatar) {
                if (this.target == source) {
                    this.target = null;
                    this.canDropFlag = false;
                    this.avatar.update();
                    dojo.publish("/dnd/source/over", [null]);
                }
            } else {
                dojo.publish("/dnd/source/over", [null]);
            }
        },
        startDrag: function(source, nodes, copy) {
            // summary: called to initiate the DnD operation
            // source: Object: the source which provides items
            // nodes: Array: the list of transferred items
            // copy: Boolean: copy items, if true, move items otherwise
            this.source = source;
            this.nodes = nodes;
            this.copy = Boolean(copy); // normalizing to true boolean
            this.avatar = this.makeAvatar();
            dojo.body().appendChild(this.avatar.node);
            dojo.publish("/dnd/start", [source, nodes, this.copy]);
            this.events = [
                dojo.connect(dojo.doc, "onmousemove", this, "onMouseMove"),
                dojo.connect(dojo.doc, "onmouseup", this, "onMouseUp"),
                dojo.connect(dojo.doc, "onkeydown", this, "onKeyDown"),
                dojo.connect(dojo.doc, "onkeyup", this, "onKeyUp")
            ];
            var c = "dojoDnd" + (copy ? "Copy" : "Move");
            dojo.addClass(dojo.body(), c);
        },
        canDrop: function(flag) {
            // summary: called to notify if the current target can accept items
            var canDropFlag = Boolean(this.target && flag);
            if (this.canDropFlag != canDropFlag) {
                this.canDropFlag = canDropFlag;
                this.avatar.update();
            }
        },
        stopDrag: function() {
            // summary: stop the DnD in progress
            dojo.removeClass(dojo.body(), "dojoDndCopy");
            dojo.removeClass(dojo.body(), "dojoDndMove");
            dojo.forEach(this.events, dojo.disconnect);
            this.events = [];
            this.avatar.destroy();
            this.avatar = null;
            this.source = null;
            this.nodes = [];
        },
        makeAvatar: function() {
            // summary: makes the avatar, it is separate to be overwritten dynamically, if needed
            return new dojo.dnd.Avatar(this);
        },
        updateAvatar: function() {
            // summary: updates the avatar, it is separate to be overwritten dynamically, if needed
            this.avatar.update();
        },
        // mouse event processors
        onMouseMove: function(e) {
            // summary: event processor for onmousemove
            // e: Event: mouse event
            var a = this.avatar;
            if (a) {
                //dojo.dnd.autoScrollNodes(e);
                dojo.dnd.autoScroll(e);
                var s = a.node.style;
                s.left = (e.pageX + this.OFFSET_X) + "px";
                s.top = (e.pageY + this.OFFSET_Y) + "px";
                var copy = Boolean(this.source.copyState(dojo.dnd.getCopyKeyState(e)));
                if (this.copy != copy) {
                    this._setCopyStatus(copy);
                }
            }
        },
        onMouseUp: function(e) {
            // summary: event processor for onmouseup
            // e: Event: mouse event
            if (this.avatar &&
                (!("mouseButton" in this.source) || this.source.mouseButton == e.button)) {
                if (this.target && this.canDropFlag) {
                    var params = [this.source, this.nodes, Boolean(this.source.copyState(dojo.dnd.getCopyKeyState(e))), this.target];
                    dojo.publish("/dnd/drop/before", params);
                    dojo.publish("/dnd/drop", params);
                } else {
                    dojo.publish("/dnd/cancel");
                }
                this.stopDrag();
            }
        },
        // keyboard event processors
        onKeyDown: function(e) {
            // summary: event processor for onkeydown:
            //	watching for CTRL for copy/move status, watching for ESCAPE to cancel the drag
            // e: Event: keyboard event
            if (this.avatar) {
                switch (e.keyCode) {
                    case dojo.keys.CTRL:
                        var copy = Boolean(this.source.copyState(true));
                        if (this.copy != copy) {
                            this._setCopyStatus(copy);
                        }
                        break;
                    case dojo.keys.ESCAPE:
                        dojo.publish("/dnd/cancel");
                        this.stopDrag();
                        break;
                }
            }
        },
        onKeyUp: function(e) {
            // summary: event processor for onkeyup, watching for CTRL for copy/move status
            // e: Event: keyboard event
            if (this.avatar && e.keyCode == dojo.keys.CTRL) {
                var copy = Boolean(this.source.copyState(false));
                if (this.copy != copy) {
                    this._setCopyStatus(copy);
                }
            }
        },
        // utilities
        _setCopyStatus: function(copy) {
            // summary: changes the copy status
            // copy: Boolean: the copy status
            this.copy = copy;
            this.source._markDndStatus(this.copy);
            this.updateAvatar();
            dojo.removeClass(dojo.body(), "dojoDnd" + (this.copy ? "Move" : "Copy"));
            dojo.addClass(dojo.body(), "dojoDnd" + (this.copy ? "Copy" : "Move"));
        }
    });

    // summary: the manager singleton variable, can be overwritten, if needed
    dojo.dnd._manager = null;

    dojo.dnd.manager = function() {
        // summary: returns the current DnD manager, creates one if it is not created yet
        if (!dojo.dnd._manager) {
            dojo.dnd._manager = new dojo.dnd.Manager();
        }
        return dojo.dnd._manager;	// Object
    };

}
